package com.such.kit.remote;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.message.Message;
import org.apache.cxf.service.Service;
import org.apache.cxf.service.model.BindingInfo;
import org.apache.cxf.service.model.BindingMessageInfo;
import org.apache.cxf.service.model.BindingOperationInfo;
import org.apache.cxf.service.model.MessagePartInfo;
import org.apache.cxf.service.model.ServiceInfo;
import com.alibaba.fastjson.JSON;
import com.such.kit.cache.CacheCommons;
import com.such.kit.file.FileCommons;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;

/**
 * @author SUCH <pre>
 * WebService 动态客户端工具类
 * 
 * WebService 参数、返回值应直接使用简单的数据类型，如时间类型可以考虑使用 string
 * 当参数、返回值过于复杂，可以使用 xml、json 进行数据打包
 * </pre>
 */
public class WebServiceCommons {

	private static String clientCache = CacheCommons.buildCacheName(WebServiceCommons.class, "client");
	private static String methodCache = CacheCommons.buildCacheName(WebServiceCommons.class, "method");
	private static String inputsCache = CacheCommons.buildCacheName(WebServiceCommons.class, "inputs");

	/**
	 * <pre>
	 * 解析 wsdl 文件
	 * 初始化方法缓存
	 * 
	 * 基于 cxf 实现
	 * 可基于 {@link Interceptor} 实现客户端认证
	 * </pre>
	 * @param address 服务地址
	 * @param interceptors 消息拦截器集合 [可空]
	 * @param fullPath 构建完整的方法路径 [true 完整的 | false 简单的]
	 * @param ignoreNamespace 忽略目标命名空间，既 tns [true 忽略 | false 保留]
	 */
	private static void parseWsdl(String address, List<Interceptor<? extends Message>> interceptors, boolean fullPath, boolean ignoreNamespace) throws Exception {
		Client client = CacheCommons.get(clientCache, address);
		if (ValidateCommons.isEmpty(client) && ValidateCommons.isNotEmpty(address)) {
			String wsdlUrl = null;
			if (address.indexOf("?") < 0) {
				wsdlUrl = StringCommons.merger(address, "?wsdl");
			}
			JaxWsDynamicClientFactory factory = JaxWsDynamicClientFactory.newInstance();
			client = factory.createClient(wsdlUrl);
			if (ValidateCommons.isNotEmpty(interceptors)) {
				client.getOutInterceptors().addAll(interceptors);
			}
			CacheCommons.put(clientCache, address, client);
			Endpoint endpoint = client.getEndpoint();
			Service service = endpoint.getService();
			List<ServiceInfo> serviceInfos =  service.getServiceInfos();
			for (ServiceInfo serviceInfo : serviceInfos) {
				QName serviceInfoName = serviceInfo.getName();
				Collection<BindingInfo> bindingInfos = serviceInfo.getBindings();
				for (BindingInfo bindingInfo : bindingInfos) {
					QName bindingInfoName = bindingInfo.getName();
					Collection<BindingOperationInfo> bindingOperationInfos = bindingInfo.getOperations();
					for (BindingOperationInfo bindingOperationInfo : bindingOperationInfos) {
						QName bindingOperationInfoName = bindingOperationInfo.getName();
						BindingMessageInfo bindingMessageInfo = null;
						if (bindingOperationInfo.isUnwrapped()) {
							BindingOperationInfo unwrappedOperation = bindingOperationInfo.getUnwrappedOperation();
							if (ValidateCommons.isNotEmpty(unwrappedOperation)) {
								bindingMessageInfo = unwrappedOperation.getInput();
							}
						} else {
							BindingOperationInfo wrappedOperation = bindingOperationInfo.getWrappedOperation();
							if (ValidateCommons.isNotEmpty(wrappedOperation)) {
								bindingMessageInfo = wrappedOperation.getInput();
							}
						}
						if (bindingMessageInfo == null) {
							bindingMessageInfo = bindingOperationInfo.getInput();
						}
						List<MessagePartInfo> messagePartInfos = bindingMessageInfo.getMessageParts();

						String method = null;
						if (fullPath) {
							if (ignoreNamespace) {
								method = FileCommons.appendPathNode(address, serviceInfoName.getLocalPart(), bindingInfoName.getLocalPart(), bindingOperationInfoName.getLocalPart());
							} else {
								method = FileCommons.appendPathNode(address, serviceInfoName.toString(), bindingInfoName.toString(), bindingOperationInfoName.toString());
							}
						} else {
							if (ignoreNamespace) {
								method = FileCommons.appendPathNode(address, bindingOperationInfoName.getLocalPart());
							} else {
								method = FileCommons.appendPathNode(address, bindingOperationInfoName.toString());
							}
						}
						CacheCommons.put(methodCache, method, bindingOperationInfoName);
						CacheCommons.put(inputsCache, method, messagePartInfos);
					}
				}
			}
		}
	}

	/**
	 * <pre>
	 * 构建执行入参
	 * </pre>
	 * @param method 方法
	 * @param params 参数集
	 * @return 入参集
	 */
	private static Object[] buildArgs(String method, Map<String, ?> params) {
		List<MessagePartInfo> inputs = CacheCommons.get(inputsCache, method);
		Object[] args = null;
		if (ValidateCommons.isNotEmpty(inputs)) {
			int inputSize = inputs.size();
			args = new Object[inputSize];
			for (int i = 0; i < inputSize; i++) {
				MessagePartInfo messagePartInfo = inputs.get(i);
				QName messagePartInfoName = messagePartInfo.getName();
				String argName = messagePartInfoName.getLocalPart();
				Object arg = params.get(argName);
				Class<?> typeClass = messagePartInfo.getTypeClass();
				if (typeClass != null) {
					String typeName = typeClass.getCanonicalName();
					if (typeName.endsWith("[]")) {
						// 通过 JSON 统一处理 List、Array 类型的数据
						arg = JSON.parseArray(JSON.toJSONString(arg), typeClass.getComponentType());
					}
				}
				args[i] = arg;
			}
		} else {
			args = new Object[0];
		}
		return args;
	}

	/**
	 * <pre>
	 * 执行远端方法
	 * </pre>
	 * @param client 客户端
	 * @param operationName 操作名
	 * @param args 入参集
	 * @return 响应报文
	 */
	private static String invoke(Client client, QName operationName, Object... args) throws Exception {
		Object[] results = client.invoke(operationName, args);
		String result = "";
		if (ValidateCommons.isNotEmpty(results)) {
			result = JSON.toJSON(results[0]).toString();
		}
		return result;
	}

	/**
	 * <pre>
	 * 执行远端方法
	 * </pre>
	 * @param address 服务地址
	 * @param interceptors 消息拦截器集合 [可空]
	 * @param targetNamespace 目标命名空间，既 tns [可空]
	 * @param operation 操作
	 * @param args 入参集
	 * @return 响应报文
	 */
	public static String invoke(String address, List<Interceptor<? extends Message>> interceptors,
			String targetNamespace, String operation, Object... args) throws Exception {
		boolean ignoreNamespace = true;
		if (ValidateCommons.isNotEmpty(targetNamespace)) {
			ignoreNamespace = false;
			operation = new QName(targetNamespace, operation).toString();
		}
		parseWsdl(address, interceptors, false, ignoreNamespace);
		String method = FileCommons.appendPathNode(address, operation);
		Client client = CacheCommons.get(clientCache, address);
		QName operationName = CacheCommons.get(methodCache, method);
		return invoke(client, operationName, args);
	}

	/**
	 * <pre>
	 * 执行远端方法
	 * </pre>
	 * @param address 服务地址
	 * @param interceptors 消息拦截器集合 [可空]
	 * @param targetNamespace 目标命名空间，既 tns [可空]
	 * @param operation 操作
	 * @param params 参数集 [可空]
	 * @return 响应报文
	 */
	public static String invoke(String address, List<Interceptor<? extends Message>> interceptors,
			String targetNamespace, String operation, Map<String, ?> params) throws Exception {
		boolean ignoreNamespace = true;
		if (ValidateCommons.isNotEmpty(targetNamespace)) {
			ignoreNamespace = false;
			operation = new QName(targetNamespace, operation).toString();
		}
		parseWsdl(address, interceptors, false, ignoreNamespace);
		String method = FileCommons.appendPathNode(address, operation);
		Client client = CacheCommons.get(clientCache, address);
		QName operationName = CacheCommons.get(methodCache, method);
		Object[] args = buildArgs(method, params);
		return invoke(client, operationName, args);
	}

	/**
	 * <pre>
	 * 执行远端方法
	 * </pre>
	 * @param address 服务地址
	 * @param targetNamespace 目标命名空间，既 tns [可空]
	 * @param operation 操作
	 * @param args 入参集
	 * @return 响应报文
	 */
	public static String invoke(String address,
			String targetNamespace, String operation, Object... args) throws Exception {
		return invoke(address, null, targetNamespace, operation, args);
	}

	/**
	 * <pre>
	 * 执行远端方法
	 * </pre>
	 * @param address 服务地址
	 * @param targetNamespace 目标命名空间，既 tns [可空]
	 * @param operation 操作
	 * @param params 参数集 [可空]
	 * @return 响应报文
	 */
	public static String invoke(String address,
			String targetNamespace, String operation, Map<String, ?> params) throws Exception {
		return invoke(address, null, targetNamespace, operation, params);
	}

	/**
	 * <pre>
	 * 执行远端方法
	 * </pre>
	 * @param address 服务地址
	 * @param interceptors 消息拦截器集合 [可空]
	 * @param operation 操作
	 * @param args 入参集
	 * @return 响应报文
	 */
	public static String invoke(String address, List<Interceptor<? extends Message>> interceptors,
			String operation, Object... args) throws Exception {
		return invoke(address, interceptors, null, operation, args);
	}

	/**
	 * <pre>
	 * 执行远端方法
	 * </pre>
	 * @param address 服务地址
	 * @param interceptors 消息拦截器集合 [可空]
	 * @param operation 操作
	 * @param params 参数集 [可空]
	 * @return 响应报文
	 */
	public static String invoke(String address, List<Interceptor<? extends Message>> interceptors,
			String operation, Map<String, ?> params) throws Exception {
		return invoke(address, interceptors, null, operation, params);
	}

	/**
	 * <pre>
	 * 执行远端方法
	 * </pre>
	 * @param address 服务地址
	 * @param operation 操作
	 * @param args 入参集
	 * @return 响应报文
	 */
	public static String invoke(String address,
			String operation, Object... args) throws Exception {
		return invoke(address, null, null, operation, args);
	}

	/**
	 * <pre>
	 * 执行远端方法
	 * </pre>
	 * @param address 服务地址
	 * @param operation 操作
	 * @param params 参数集 [可空]
	 * @return 响应报文
	 */
	public static String invoke(String address,
			String operation, Map<String, ?> params) throws Exception {
		return invoke(address, null, null, operation, params);
	}

}
